/**
 * Copyright (c) 2018-2024, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: pipe
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2024-11-09     JasonHu           Init
 */

#include <base/pipe.h>
#include <base/malloc.h>
#include <base/page.h>
#include <base/string.h>
#include <base/thread.h>
#include <base/spin.h>
#include <base/log.h>

NX_LIST_HEAD(PipeListHead);
NX_SPIN_DEFINE_UNLOCKED(PipeListLock);

#define NX_PIPE_SIZE_DEFAULT_VALUE  NX_PAGE_SIZE

NX_PRIVATE NX_Error CreatePipeFifo(NX_Pipe *pipe, NX_Size size, NX_U8 flags)
{
    pipe->fifoMaster = pipe->fifoSlaver = NX_NULL;

    if (flags & NX_PIPE_SIMPLEX)
    {
        if (flags & NX_PIPE_SEND)
        {
            pipe->fifoMaster = NX_FifoCreate(size);
            if (!pipe->fifoMaster)
            {
                return NX_ENOMEM;
            }
        }
        else if (flags & NX_PIPE_RECV)
        {
            pipe->fifoSlaver = NX_FifoCreate(size);
            if (!pipe->fifoSlaver)
            {
                return NX_ENOMEM;
            }
        }
        else
        {
            return NX_EINVAL;
        }
    }
    else if (flags & NX_PIPE_DUPLEX)
    {
        pipe->fifoMaster = NX_FifoCreate(size);
        if (!pipe->fifoMaster)
        {
            return NX_ENOMEM;
        }
        pipe->fifoSlaver = NX_FifoCreate(size);
        if (!pipe->fifoSlaver)
        {
            NX_FifoDestroy(pipe->fifoMaster);
            return NX_ENOMEM;
        }
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error DestroyPipeFifo(NX_Pipe *pipe)
{
    if (pipe->fifoMaster != NX_NULL)
    {
        NX_FifoDestroy(pipe->fifoMaster);
    }
    if (pipe->fifoSlaver != NX_NULL)
    {
        NX_FifoDestroy(pipe->fifoSlaver);
    }
    return NX_EOK;
}

NX_INLINE NX_Pipe *SearchPipe(const char *name)
{
    NX_UArch level;
    NX_Pipe *pipe, *tmp;

    if (!name)
    {
        return NX_NULL;
    }

    pipe = NX_NULL;
    NX_SpinLockIRQ(&PipeListLock, &level);
    NX_ListForEachEntry(tmp, &PipeListHead, list)
    {
        if (!NX_StrCmp(tmp->name, name))
        {
            pipe = tmp;
            break;
        }
    }
    NX_SpinUnlockIRQ(&PipeListLock, level);

    return pipe;
}

NX_Pipe *NX_PipeCreate(const char *name, NX_Size size, NX_U8 flags)
{
    NX_Pipe *pipe;
    NX_UArch level;

    /* check name */
    if (!name)
    {
        flags |= NX_PIPE_ANONYMOUSE;
    }
    else
    {
        flags |= NX_PIPE_NAMED;
    }

    /* check buffer len */
    if (size == NX_PIPE_SIZE_DEFAULT)
    {
        size = NX_PIPE_SIZE_DEFAULT_VALUE;
    }

    /* check work mode, one of simplex or duplex */
    if (!(flags & (NX_PIPE_SEND | NX_PIPE_RECV)))
    {
        return NX_NULL;
    }

    if ((flags & (NX_PIPE_SEND | NX_PIPE_RECV)) == (NX_PIPE_SEND | NX_PIPE_RECV))
    {
        flags |= NX_PIPE_DUPLEX;
    } else if ((flags & (NX_PIPE_SEND)) || (flags & (NX_PIPE_RECV)))
    {
        flags |= NX_PIPE_SIMPLEX;
    }
    
    /* check name exist */
    if ((flags & NX_PIPE_NAMED) && SearchPipe(name) != NX_NULL)
    {
        return NX_NULL;
    }

    /* alloc pipe */
    pipe = NX_MemAlloc(sizeof(NX_Pipe));
    if (!pipe)
    {
        return NX_NULL;
    }

    if (CreatePipeFifo(pipe, size, flags) != NX_EOK)
    {
        NX_MemFree(pipe);
        return NX_NULL;
    }

    if (name != NX_NULL)
    {
        NX_StrCopyN(pipe->name, name, NX_PIPE_NAME_LEN);
    }

    pipe->size = size;
    pipe->flags = flags;
    pipe->masterTid = NX_ThreadSelf()->tid;

    NX_AtomicSet(&pipe->refernce, 0);
    NX_AtomicSet(&pipe->masterRef, 0);
    NX_AtomicSet(&pipe->slaverRef, 0);
    NX_AtomicSet(&pipe->connectRef, 0);

    NX_ListInit(&pipe->list);

    NX_WaitQueueInit(&pipe->sendQueue);
    NX_WaitQueueInit(&pipe->recvQueue);

    NX_MutexInit(&pipe->masterMutex);
    NX_MutexInit(&pipe->slaverMutex);

    if (flags & NX_PIPE_NAMED)
    {
        /* insert pipe */
        NX_SpinLockIRQ(&PipeListLock, &level);
        NX_ListAdd(&pipe->list, &PipeListHead);
        NX_SpinUnlockIRQ(&PipeListLock, level);
    }
    return pipe;
}

NX_Error NX_PipeDestroy(NX_Pipe *pipe)
{
    NX_UArch level;
    NX_Error err;

    if (!pipe)
    {
        return NX_EINVAL;
    }

    err = DestroyPipeFifo(pipe);
    if (err != NX_EOK)
    {
        return err;
    }

    if (pipe->flags & NX_PIPE_NAMED)
    {
        /* remove pipe */
        NX_SpinLockIRQ(&PipeListLock, &level);
        NX_ListDel(&pipe->list);
        NX_SpinUnlockIRQ(&PipeListLock, level);
    }

    NX_MemFree(pipe);
    return NX_EOK;
}

NX_Error NX_PipeSend(NX_Pipe *pipe, void *buffer, NX_Size len, NX_Size *nsend)
{
    NX_Fifo *fifo;
    NX_Size leftSize;
    NX_Size off = 0;
    NX_U8 *buf = buffer;
    NX_Size chunk = 0;
    NX_Size wrsize = 0;
    NX_Atomic *targetRef;
    NX_Mutex *mutex;

    if (!pipe || !buf || !len)
    {
        return NX_EINVAL;
    }

    if (NX_PipeIsMaster(pipe))
    {
        /* simplex recv pipe can not send in master */
        if (pipe->flags & NX_PIPE_SIMPLEX &&
            pipe->flags & NX_PIPE_RECV)
        {
            return NX_EPERM;
        }
        fifo = pipe->fifoMaster;
        targetRef = &pipe->slaverRef;
        mutex = &pipe->masterMutex;
    }
    else
    {
        /* simplex send pipe can not send in slaver */
        if (pipe->flags & NX_PIPE_SIMPLEX &&
            pipe->flags & NX_PIPE_SEND)
        {
            return NX_EPERM;
        }
        fifo = pipe->fifoSlaver;
        targetRef = &pipe->masterRef;
        mutex = &pipe->slaverMutex;
    }

    NX_MutexLock(mutex);
    /* send if buffer avaliable, or not wait */
    leftSize = len;
    while (leftSize > 0)
    {
        while (NX_FifoAvaliable(fifo) <= 0)
        {
            /* check nowait */
            if (pipe->flags & NX_PIPE_NOWAIT)
            {
                goto end;
            }

            /* check pipe error */
            if (NX_AtomicGet(&pipe->connectRef) > 0 && NX_AtomicGet(targetRef) <= 0)
            {
                NX_SignalSend(NX_ThreadSelf()->tid, NX_SIGNAL_TERMINATE, NX_NULL, NX_NULL);
                NX_MutexUnlock(mutex);
                return NX_EFAULT;
            }
            
            /* wakeup recver */
            NX_WaitQueueWakeUp(&pipe->recvQueue);
            NX_MutexUnlock(mutex);
            /* wait on fifo */
            NX_WaitQueueBlock(&pipe->sendQueue, NX_FifoAvaliable(fifo) > 0);
            NX_MutexLock(mutex);
        }
        
        /* write into fifo */
        chunk = NX_MIN(leftSize, pipe->size);
        chunk = NX_MIN(chunk, NX_FifoAvaliable(fifo));
        chunk = NX_FifoWrite(fifo, buf + off, chunk);
        off += chunk;
        leftSize -= chunk;
        wrsize += chunk;
    }

    /* wakeup recver at end */
    NX_WaitQueueWakeUp(&pipe->recvQueue);
    
end:
    NX_MutexUnlock(mutex);
    if (nsend)
    {
        *nsend = wrsize;
    }
    return NX_EOK;
}

NX_Error NX_PipeRecv(NX_Pipe *pipe, void *buffer, NX_Size len, NX_Size *nrecv)
{
    NX_Fifo *fifo;
    NX_Size rdsize = 0;
    NX_Size chunk;
    NX_Size leftSize;
    NX_U8 *buf = buffer;
    NX_Size off = 0;
    NX_Atomic *targetRef;
    NX_Mutex *mutex;

    if (!pipe || !buf || !len)
    {
        return NX_EINVAL;
    }

    if (NX_PipeIsMaster(pipe))
    {
        /* simplex send pipe can not recv in master */
        if (pipe->flags & NX_PIPE_SIMPLEX &&
            pipe->flags & NX_PIPE_SEND)
        {
            return NX_EPERM;
        }
        fifo = pipe->fifoSlaver;
        targetRef = &pipe->slaverRef;
        mutex = &pipe->slaverMutex;
    }
    else
    {
        /* simplex recv pipe can not recv in slaver */
        if (pipe->flags & NX_PIPE_SIMPLEX &&
            pipe->flags & NX_PIPE_RECV)
        {
            return NX_EPERM;
        }
        fifo = pipe->fifoMaster;
        targetRef = &pipe->masterRef;
        mutex = &pipe->masterMutex;
    }

    NX_MutexLock(mutex);
    leftSize = len;
    while (leftSize > 0)
    {
        while (NX_FifoLen(fifo) <= 0)
        {
            /* check no wait */
            if (pipe->flags & NX_PIPE_NOWAIT)
            {
                goto end;
            }

            /* check pipe disconnect */
            if (NX_AtomicGet(targetRef) <= 0)
            {
                goto end;
            }
            
            /* wakeup sender */
            NX_WaitQueueWakeUp(&pipe->sendQueue);
            
            NX_MutexUnlock(mutex);
            /* wait on pipe */
            NX_WaitQueueBlock(&pipe->recvQueue, NX_FifoLen(fifo) > 0);    
            NX_MutexLock(mutex);
        }
        /* read from pipe */
        chunk = NX_MIN(leftSize, pipe->size);
        chunk = NX_MIN(chunk, NX_FifoLen(fifo));
        chunk = NX_FifoRead(fifo, buf + off, chunk);
        off += chunk;
        rdsize += chunk;
        leftSize -= chunk;
    }

end:
    NX_MutexUnlock(mutex);

    if (nrecv)
    {
        *nrecv = rdsize;
    }

    /* wakeup blocked sender */
    NX_WaitQueueWakeUp(&pipe->sendQueue);

    return NX_EOK;
}

NX_Pipe *NX_PipeConnectByPipe(NX_Pipe *pipe, NX_Bool isMaster)
{
    if (!pipe)
    {
        return NX_NULL;
    }
    
    if (isMaster)
    {
        NX_AtomicInc(&pipe->masterRef);
    }
    else
    {
        NX_AtomicInc(&pipe->slaverRef);
        NX_AtomicInc(&pipe->connectRef);
    }
    return pipe;
}

NX_Error NX_PipeDisconnect(NX_Pipe *pipe)
{
    if (!pipe)
    {
        return NX_EINVAL;
    }
    
    if (NX_PipeIsMaster(pipe))
    {
        NX_AtomicDec(&pipe->masterRef);
        if (NX_AtomicGet(&pipe->masterRef) < 0)
        {
            NX_AtomicSet(&pipe->masterRef, 0);
        }
    }
    else
    {
        NX_AtomicDec(&pipe->slaverRef);
        NX_AtomicDec(&pipe->connectRef);

        if (NX_AtomicGet(&pipe->slaverRef) < 0)
        {
            NX_AtomicSet(&pipe->slaverRef, 0);
        }
        if (NX_AtomicGet(&pipe->connectRef) < 0)
        {
            NX_AtomicSet(&pipe->connectRef, 0);
        }
    }
    return NX_EOK;
}

NX_Pipe *NX_PipeConnectByName(const char *name)
{
    NX_Pipe *pipe;
    pipe = SearchPipe(name);
    if (pipe)
    {
        pipe = NX_PipeConnectByPipe(pipe, NX_PipeIsMaster(pipe));
    }

    return pipe;
}

NX_Error NX_PipeIncrease(NX_Pipe *pipe)
{
    if (!pipe)
    {
        return NX_EINVAL;
    }
    NX_AtomicInc(&pipe->refernce);
    return NX_EOK;
}

NX_Error NX_PipeDecrease(NX_Pipe *pipe)
{
    if (!pipe)
    {
        return NX_EINVAL;
    }
    NX_AtomicDec(&pipe->refernce);
    if (NX_AtomicGet(&pipe->refernce) <= 0)
    {
        NX_PipeDestroy(pipe);
    }
    return NX_EOK;
}
